﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;

namespace Talk.Net.Server
{
	class SocketServer
	{
		private string LocalAddress = "127.0.0.1";
		private int LocalPort = 7000;
		private int MaximumServerQueue = 10;
		private bool Sentinel = false;
		private List<Socket> clientSockets;
		
		private ManualResetEvent EventNotifier;
		private IPEndPoint serverEndPoint;
		private Socket serverSocket;
		private Thread serverThread;

		public delegate void LogEventDelegate(string message);
		public event LogEventDelegate LogEvent;

		public SocketServer()
		{
			// Initialize Objects
			Initialize();
		}

		private void Initialize()
		{
			EventNotifier = new ManualResetEvent(false);
			clientSockets = new List<Socket>();
		}

		public void Shutdown()
		{
			LogEvent("Server going offline.");

			// unblocks the listen loop, terminating the thread.
			Sentinel = true;
			Socket shutdownSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			shutdownSocket.Connect(serverEndPoint);
			shutdownSocket = null;

			// clean up server socket listener
			serverSocket.Close();

			LogEvent("Network layer shutdown.");
		}

		public void Startup()
		{
			LogEvent("Initializing Network Layer.");
			serverEndPoint = new IPEndPoint(IPAddress.Parse(LocalAddress), LocalPort);
			serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

			serverSocket.Bind(serverEndPoint);
			serverSocket.Listen(MaximumServerQueue);

			ThreadStart ts = new ThreadStart(Listen);
			serverThread = new Thread(ts);
			serverThread.IsBackground = true;
			serverThread.Start();

			LogEvent("Server Online. /flex");
		}

		private void Listen()
		{
			while (!Sentinel)
			{
				EventNotifier.Reset();
				serverSocket.BeginAccept(new AsyncCallback(this.AcceptConnection), serverSocket);
				EventNotifier.WaitOne();
			}
		}

		private void AcceptConnection(IAsyncResult ar)
		{
			// ignore if server shutdown imminent
			if (Sentinel)
			{
				return;
			}

			// Get the socket that handles the client request.
			Socket serverSocket = (Socket)ar.AsyncState;
			Socket clientSocket = serverSocket.EndAccept(ar);

			clientSockets.Add(clientSocket);

			LogEvent("Received Connection.");
			Client stub = new Client(clientSocket);
			clientSocket.BeginReceive(stub.buffer, 0, stub.socket.Available, SocketFlags.None, new AsyncCallback(ReceiveData), stub);
			EventNotifier.Set();
		}

		private void ReceiveData(IAsyncResult ar)
		{
			try
			{
				Client stub = (Client)ar.AsyncState;

				// move the content to the manager
				int bytes = stub.socket.EndReceive(ar);

				if (bytes > 0)
				{
					byte[] temp = new byte[bytes];
					for (int i = 0; i < bytes; i++)
					{
						temp[i] = stub.buffer[i];
					}
					ContentManager.AddContent(temp);
				}

				int available = stub.socket.Available;
				stub.Recieved(available);

				if (!stub.isDead())
				{
					stub.socket.BeginReceive(stub.buffer, 0, available, SocketFlags.None, new AsyncCallback(ReceiveData), stub);
					// performance issue with too much logging
					// LogEvent("Communication from " + stub.socket.RemoteEndPoint.ToString() + " [ " + available + " ] ");
				}
				else
				{
					// handle cleanup of socket from clientSockets connection
					LogEvent("Lost Connection.");
				}
			}
			catch (System.Net.Sockets.SocketException se)
			{
				LogEvent(se.Message);
			}
			catch (Exception e)
			{

			}
		}
	}
}
